/*
fusc.h: FuDePAN SVG Creation Library header file.
    Copyright (C) 2009  Daniel Gutson, FuDePAN

    This file is part of fusc.

    fusc is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    fusc is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with fusc.  If not, see <http://www.gnu.org/licenses/>.

*/

#ifndef FUSC_H
#define FUSC_H

#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
using namespace std;

typedef float SVGCoord;

enum _Inherit
{
    Inherit
};

enum TextAnchors
{
    Start,
    Middle,
    End
};

enum FontStyles
{
    Normal,
    Italic,
    Oblique
};
// TODO: font-weight, 

struct SVG_Attr_Base
{
    const string name;
    bool set;
    void unset() { set = false; }

    SVG_Attr_Base(const string& name) : name(name), set(false) {}
};

template <class T>
struct SVG_Attr : SVG_Attr_Base
{
    T value;

    SVG_Attr(const string& name) : SVG_Attr_Base(name), value(){}
    SVG_Attr(const string& name, const T& value)
        : SVG_Attr_Base(name), value(value)
    {}

    void set_value(const T& new_value)
    {
        value = new_value;
        set = true;
    }

    operator T() const { return value; }
};

#if 0
    TODO
template <class T>
class SVG_Attr_Enum : public SVG_Attr<T>
{
private:
    bool _inherited;
};
#endif

template <class T>
inline ostream& operator << (ostream& os, const SVG_Attr<T>& attr)
{
    if (attr.set)
        os << " " << attr.name << "=\"" << attr.value << "\"";

    return os;
}

template <class T>
inline void adjust_int(T& t)
{
    if (t > int(t))
        t = T(int(t)+1);
}

struct SVG
{
    SVG_Attr<string> fill;
    SVG_Attr<string> stroke;
    SVG_Attr<size_t> stroke_width;
    SVG_Attr<string> font_family;
    SVG_Attr<size_t> font_size;
    SVG_Attr<TextAnchors> text_anchor;
    SVG_Attr<FontStyles> font_style;
    const string desc;

    SVGCoord width;
    SVGCoord height;

    SVGCoord last_x;
    SVGCoord last_y;

    stringstream ss;

    SVG(const string& desc = string())
        : fill("fill"),
          stroke("stroke"),
          stroke_width("stroke-width"),
          font_family("font-family"),
          font_size("font-size"),
          text_anchor("text-anchor"),
          font_style("font-style"),
          desc(desc),
          width(0),
          height(0),
          last_x(0),
          last_y(0)
    {}

    void add_fill_stroke_width()
    {
        ss << fill << stroke << stroke_width;
    }

    void augment_to(SVGCoord x1, SVGCoord y1, SVGCoord x2, SVGCoord y2)
    {
        
        width  = std::max(width,  std::max(x1, x2));
        height = std::max(height, std::max(y1, y2));

        adjust_int(width);
        adjust_int(height);
    }

    void set_last_xy(SVGCoord x, SVGCoord y)
    {
        last_x = x;
        last_y = y;
    }

    void get_last_xy(SVGCoord& x, SVGCoord& y) const
    {
        x = last_x;
        y = last_y;
    }

    void clear()
    {
        ss.clear();
    }
};

template <class T>
struct ManipulatorSetter
{
    SVG_Attr<T> (SVG::* attr);
    const T new_value;

    ManipulatorSetter(SVG_Attr<T> (SVG::* attr), const T& value)
        : attr(attr), new_value(value)
    {}
};

inline ManipulatorSetter<string> fill(const string& value)
{
    return ManipulatorSetter<string>(&SVG::fill, value);
}

inline ManipulatorSetter<string> stroke(const string& value)
{
    return ManipulatorSetter<string>(&SVG::stroke, value);
}

inline ManipulatorSetter<size_t> stroke_width(size_t value)
{
    return ManipulatorSetter<size_t>(&SVG::stroke_width, value);
}

inline ManipulatorSetter<string> font_family(const string& value)
{
    return ManipulatorSetter<string>(&SVG::font_family, value);
}

inline ManipulatorSetter<size_t> font_size(size_t value)
{
    return ManipulatorSetter<size_t>(&SVG::font_size, value);
}

inline ManipulatorSetter<TextAnchors> text_anchor(TextAnchors value)
{
    return ManipulatorSetter<TextAnchors>(&SVG::text_anchor, value);
}

inline ManipulatorSetter<FontStyles> font_style(FontStyles value)
{
    return ManipulatorSetter<FontStyles>(&SVG::font_style, value);
}

template <class T>
inline SVG& operator << (SVG& svg, const ManipulatorSetter<T>& manip)
{
    (svg.*(manip.attr)).set_value(manip.new_value);
    return svg;
}

template <class T>
struct ManipulatorUnsetter
{
    SVG_Attr<T> (SVG::* attr);


    ManipulatorUnsetter(SVG_Attr<T> (SVG::* attr)) : attr(attr){}
};

extern const ManipulatorUnsetter<string>
    nofill,
    nostroke,
    nofont_family
;

extern const ManipulatorUnsetter<size_t>
    nostroke_width,
    nofont_size
;

extern const ManipulatorUnsetter<TextAnchors>
    notext_anchor
;

extern const ManipulatorUnsetter<FontStyles>
    nofont_style
;

template <class T>
inline SVG& operator << (SVG& svg, const ManipulatorUnsetter<T>& manip)
{
    (svg.*(manip.attr)).unset();
    return svg;
}

ostream& operator << (ostream& os, const SVG& svg);

class SVGShape
{
    const string elem;
    const bool has_content;
    virtual void set_attributes(SVG& svg) const = 0;
    virtual void set_content(SVG& svg) const {}
protected:
    SVGShape(const string& elem, bool has_content = false)
        : elem(elem), has_content(has_content){}

    template <class T>
    void set_attr(SVG& svg, const string& attr, const T& value) const
    {
        svg.ss << " " << attr << "=\"" << value << "\"";
    }
public:
    void manipulate(SVG& svg) const
    {
        svg.ss << "<" << elem;
        set_attributes(svg);
        if (has_content)
        {
            svg.ss << ">";
            set_content(svg);
            svg.ss << "</" << elem << ">\n";
        }
        else
            svg.ss << "/>\n";
    }
};

inline SVG& operator << (SVG& svg, const SVGShape& shape)
{
    shape.manipulate(svg);
    return svg;
}

class rectangle : public SVGShape
{
    const SVGCoord x;
    const SVGCoord y;
    const SVGCoord w;
    const SVGCoord h;

    virtual void set_attributes(SVG& svg) const
    {
        set_attr(svg, "x", x);
        set_attr(svg, "y", y);
        set_attr(svg, "width", w);
        set_attr(svg, "height", h);
        svg.add_fill_stroke_width();
        svg.augment_to(x,y, x+w, y+h);
    }
public:
    rectangle(SVGCoord x, SVGCoord y, SVGCoord width, SVGCoord height)
        : SVGShape("rect"), x(x), y(y), w(width), h(height)
    {}
};

class circle : public SVGShape
{
    const SVGCoord cx;
    const SVGCoord cy;
    const SVGCoord r;

    virtual void set_attributes(SVG& svg) const
    {
        set_attr(svg, "cx", cx);
        set_attr(svg, "cy", cy);
        set_attr(svg, "r", r);
        svg.add_fill_stroke_width();
        svg.augment_to(cx-r,cy-r, cx+r, cy+r);
    }
public:
    circle(SVGCoord cx, SVGCoord cy, size_t r)
        : SVGShape("circle"), cx(cx), cy(cy), r(r)
    {}
};

class line : public SVGShape
{
    bool  from_last_xy;
    const SVGCoord x1;
    const SVGCoord y1;
    const SVGCoord x2;
    const SVGCoord y2;

    virtual void set_attributes(SVG& svg) const
    {
        SVGCoord xx1;
        SVGCoord yy1;

        if (from_last_xy)
            svg.get_last_xy(xx1, yy1);
        else
        {
            xx1 = x1;
            yy1 = y1;
        }

        set_attr(svg, "x1", xx1);
        set_attr(svg, "y1", yy1);
        set_attr(svg, "x2", x2);
        set_attr(svg, "y2", y2);
        svg.ss << svg.stroke << svg.stroke_width;
        svg.augment_to(x1, y1, x2, y2);
        svg.set_last_xy(x2, y2);
    }
public:
    line(SVGCoord x1, SVGCoord y1, SVGCoord x2, SVGCoord y2)
        : SVGShape("line"), from_last_xy(false), x1(x1), y1(y1), x2(x2), y2(y2)
    {}

    line(SVGCoord x2, SVGCoord y2)
        : SVGShape("line"), from_last_xy(true), x1(0), y1(0), x2(x2), y2(y2)
    {}
};

class ellipse : public SVGShape
{
    const SVGCoord cx;
    const SVGCoord cy;
    const size_t rx;
    const size_t ry;

    virtual void set_attributes(SVG& svg) const
    {
        set_attr(svg, "cx", cx);
        set_attr(svg, "cy", cy);
        set_attr(svg, "rx", rx);
        set_attr(svg, "ry", ry);
        svg.add_fill_stroke_width();
        svg.augment_to(cx-rx,cy-ry, cx+rx, cy+ry);
    }
public:
    ellipse(SVGCoord cx, SVGCoord cy, size_t rx, size_t ry)
        : SVGShape("ellipse"), cx(cx), cy(cy), rx(rx), ry(ry)
    {}
};

class text : public SVGShape
{
    const SVGCoord x;
    const SVGCoord y;
    const string txt;

    virtual void set_attributes(SVG& svg) const
    {
        static const char* anchors[] = { "start", "middle", "end" };
        static const char* styles[] = { "normal", "italic", "oblique" };
        set_attr(svg, "x", x);
        set_attr(svg, "y", y);
        svg.ss << svg.font_family;
        svg.ss << svg.font_size;
        if (svg.text_anchor.set)
            set_attr(svg, svg.text_anchor.name, anchors[svg.text_anchor.value]);
        if (svg.font_style.set)
            set_attr(svg, svg.font_style.name, styles[svg.font_style.value]);

        svg.augment_to(x, y, x + svg.font_size * txt.length(), y + svg.font_size);
    }

    virtual void set_content(SVG& svg) const
    {
        svg.ss << txt;
    }

public:
    text(SVGCoord x, SVGCoord y, const string& txt)
        : SVGShape("text", true), x(x), y(y), txt(txt)
    {}
};

#endif
